﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace FCSChart.Graphical
{
    /// <summary>
    /// 多边形--多个点连线形成的形状
    /// </summary>
    public class PolygonGraphical : BaseGraphical
    {
        private List<Point> points;
        /// <summary>
        /// 多边形的点位置--真实数值数据，需要通过轴的GetLocation获取真实的位置
        /// </summary>
        public List<Point> Points
        {
            get { return points; }
            protected set { points = value; OnPropertyChanged("Points"); }
        }

        public PolygonGraphical()
        {
            Points = new List<Point>();
        }
        public PolygonGraphical(PolygonGraphicalModel model) : base(model)
        {
            this.Points = model.Points;
            if (model.AreaNames != null && model.AreaNames.Length == 1)
            {
                Areas = model.AreaNames.Select(p => new GraphicalArea() { Name = p, OwnerGraphical = this }).ToArray();
                Helper.AddExistedGraphicalName(this.ShortName, model.AreaNames.ToArray());
                if (model.AreaColors != null && model.AreaColors.Length == 1)
                {
                    Areas[0].DisplayColor = model.AreaColors[0];
                }
            }
        }
        protected override void InitName()
        {
            this.Name = "Polygon";
            this.ShortName = "P";
        }
        #region function
        /// <summary>
        /// 绘制图形
        /// </summary>
        internal override void Drawing()
        {
            if (OwnerChart == null || !OwnerChart.IsLoaded
                || OwnerChart.XAxis == null || !OwnerChart.XAxis.IsLoaded || OwnerChart.XAxis.ActualWidth == 0
                || OwnerChart.YAxis == null || !OwnerChart.YAxis.IsLoaded || OwnerChart.YAxis.ActualHeight == 0) return;
            if (GraphicalShape == null)
            {
                Binding binding = new Binding("Points") { Source = this, Converter = Converters.PolygonPointsToPointCollectionConverter.Converter, ConverterParameter = OwnerChart, Mode = BindingMode.OneWay };
                var temp = new Polygon() { FillRule = FillRule.Nonzero, Cursor = Cursors.Hand };
                temp.SetBinding(Polygon.PointsProperty, binding);
                temp.SetBinding(Shape.FillProperty, new Binding("Fill") { Source = this });
                temp.SetBinding(Shape.StrokeProperty, new Binding("Stroke") { Source = this });
                temp.SetBinding(Shape.StrokeThicknessProperty, new Binding("StrokeThickness") { Source = this });
                temp.SetBinding(FrameworkElement.ContextMenuProperty, new Binding("ContextMenu") { Source = this });
                temp.SetBinding(UIElement.FocusableProperty, new Binding("CanChangeGraphical") { Source = this.OwnerChart });
                temp.MouseDown += Graphical_MouseDown;
                temp.MouseMove += Graphical_MouseMove;
                temp.MouseLeftButtonUp += (sender, e) =>
                {
                    if (this.OwnerChart.CanChangeGraphical && !IsCreateing) RefreshAreaSource(this.OwnerChart.XValueConverter, this.OwnerChart.YValueConverter);
                };
                temp.KeyDown += (sender, e) =>
                {
                    if (this.OwnerChart.CanChangeGraphical)
                    {
                        if (e.Key == Key.Left)
                        {
                            Move(-1, 0); e.Handled = true;
                        }
                        else if (e.Key == Key.Right)
                        {
                            Move(1, 0); e.Handled = true;
                        }
                        else if (e.Key == Key.Up)
                        {
                            Move(0, -1); e.Handled = true;
                        }
                        else if (e.Key == Key.Down)
                        {
                            Move(0, 1); e.Handled = true;
                        }
                    }
                };
                GraphicalShape = temp;
            }
            OnPropertyChanged("Points");
            if (!IsCreateing) DrawingControl();
        }

        #region panel event--用户门创建
        /// <summary>
        /// 鼠标按下
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        internal override void PanelMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (IsCreateing && sender is Panel panel && Points.Count <= 0)
            {
                var point = e.GetPosition(panel);
                var x = OwnerChart.XAxis.GetLocationValue(point.X);
                var y = OwnerChart.YAxis.GetLocationValue(point.Y);
                Points.Add(new Point(x, y));
            }
        }
        /// <summary>
        /// 鼠标移动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        internal override void PanelMouseMove(object sender, MouseEventArgs e)
        {
            if (IsCreateing && sender is Panel panel && Points.Count > 0)
            {
                var point = e.GetPosition(panel);
                var x = OwnerChart.XAxis.GetLocationValue(point.X);
                var y = OwnerChart.YAxis.GetLocationValue(point.Y);
                Points[0] = new Point(x, y);
                OnPropertyChanged("Points");
            }
            else if (!IsCreateing && ControledShape != null)
            {
                Shape_MouseMove(ControledShape, e);
            }
        }
        /// <summary>
        /// 鼠标抬起
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        internal override void PanelMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (IsCreateing && sender is Panel panel)
            {
                var point = e.GetPosition(panel);
                var x = OwnerChart.XAxis.GetLocationValue(point.X);
                var y = OwnerChart.YAxis.GetLocationValue(point.Y);
                var tempPoint = new Point(x, y);
                if (Points.Count >= 4 && Points[0].Equals(Points[Points.Count - 1]))
                {
                    Points.RemoveAt(0);
                    OnPropertyChanged("Points");
                    IsCreateing = false;
                }
                else
                {
                    Points.Add(tempPoint);
                    OnPropertyChanged("Points");
                }
            }
            else if (ControledShape != null)
            {
                Shape_MouseLeftButtonUp(ControledShape, e);
                ControledShape = null;
            }
        }

        #endregion

        /// <summary>
        /// 移动门
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        internal override void Move(double x, double y)
        {
            for (int i = 0; i < Points.Count; i++)
            {
                var point = Points[i];
                var xtemp = OwnerChart.XAxis.GetLocationValue(OwnerChart.XAxis.GetValueLocation(point.X) + x);
                var ytemp = OwnerChart.YAxis.GetLocationValue(OwnerChart.YAxis.GetValueLocation(point.Y) + y);
                Points[i] = new Point(xtemp, ytemp);
            }
            OnPropertyChanged("Points");
            DrawingControl();
        }
        #endregion

        /// <summary>
        /// 绘制控制按钮
        /// </summary>
        protected override void DrawingControl()
        {
            if (GraphicalShape is Polygon graphical)
            {
                if (Areas == null) Areas = new GraphicalArea[] { new GraphicalArea() { Name = OwnerChart.CreateNewAreaNameFunction(this), OwnerGraphical = this } };
                this.OwnerChart.AddGraphicalArea(Areas);
                var points = graphical.Points;
                if (ControlShapes.Count > points.Count)
                    for (int i = ControlShapes.Count - 1; i >= points.Count; i--)
                        ControlShapes.RemoveAt(i);
                for (int i = 0; i < points.Count; i++)
                {
                    var point = points[i];
                    if (ControlShapes.Count > i)
                    {
                        if (ControlShapes[i] is Path path && path.Data is EllipseGeometry ellipse)
                            ellipse.Center = point;
                    }
                    else
                    {
                        ControlShapes.Add(new Path() { Data = new EllipseGeometry(point, 5, 5), Cursor = Cursors.SizeAll });
                    }
                }
                Areas[0].Center = new Point(points.Average(p => p.X), points.Average(p => p.Y));
            }
        }
        /// <summary>
        /// 移动门的控制按钮
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected override void Shape_MouseMove(object sender, MouseEventArgs e)
        {
            if (this.OwnerChart.CanChangeGraphical && e.LeftButton == MouseButtonState.Pressed && sender is Path path && path.Data is EllipseGeometry ellipse)
            {
                var point = e.GetPosition(OwnerChart.ViewPanel);
                var index = ControlShapes.IndexOf(path);
                var value = Converters.AxisValueToViewValueConverter.Converter.ConvertBack(point, null, OwnerChart, null);
                if (value is Point p)
                {
                    Points[index] = p;
                    OnPropertyChanged("Points");
                }
                ellipse.Center = point;
                e.Handled = true;
                var points = ControlShapes.Where(x => x is Path temp && temp.Data is EllipseGeometry g).Select(x => ((EllipseGeometry)((Path)x).Data).Center).ToList();
                Areas[0].Center = new Point(points.Average(x => x.X), points.Average(x => x.Y));
            }
        }
        /// <summary>
        /// 更新门划分的区域内数据
        /// </summary>
        internal override async void RefreshAreaSource(Func<object, double> xValueConverter, Func<object, double> yValueConverter)
        {
            if (Areas == null || Areas.Length != 1) return;
            if (!isCreateing && Points != null && Points.Count >= 3 && OwnerChart != null && OwnerChart.XSource != null && OwnerChart.YSource != null && OwnerChart.XAxis != null && OwnerChart.YAxis != null)
            {
                List<Point> axisViewPoints = new List<Point>();
                foreach (var point in Points) axisViewPoints.Add(new Point(OwnerChart.XAxis.ValueToAxisValue(point.X), OwnerChart.YAxis.ValueToAxisValue(point.Y)));
                var maxX = axisViewPoints.Max(p => p.X);
                var minX = axisViewPoints.Min(p => p.X);
                var maxY = axisViewPoints.Max(p => p.Y);
                var minY = axisViewPoints.Min(p => p.Y);
                int nvert = axisViewPoints.Count;
                var xSource = OwnerChart.XSource;
                var ySource = OwnerChart.YSource;
                var count = Math.Min(xSource.Count, ySource.Count);
                var parentIndexs = OwnerChart.Indexs;
                Func<double, double> XAxisFunc = OwnerChart.XAxis.ValueToAxisValue;
                Func<double, double> YAxisFunc = OwnerChart.YAxis.ValueToAxisValue;

                var maxDegreeOfParallelism = OwnerChart.Series == null ? 4 : OwnerChart.Series.MaxDegreeOfParallelism;
                try
                {
                    IsRefreshingAreaSource = true;
                    if (CancelTokenSource != null)
                    {
                        CancelTokenSource.Cancel();
                        CancelTokenSource.Dispose();
                    }
                    CancelTokenSource = new CancellationTokenSource();
                    var indexstemp = await Task.Factory.StartNew((tkn) =>
                    {
                        if (tkn is CancellationToken token)
                        {
                            ConcurrentBag<int> indexs = new ConcurrentBag<int>();
                            if (parentIndexs == null)
                            {
                                try
                                {
                                    var result = Parallel.For(0, count, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                                    {
                                        if (loop.IsStopped) return;
                                        if (xSource.Count <= i || ySource.Count <= i || token.IsCancellationRequested) loop.Stop();
                                        else
                                        {
                                            var tempx = XAxisFunc(xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]));
                                            var tempy = YAxisFunc(yValueConverter == null ? Convert.ToDouble(ySource[i]) : yValueConverter(ySource[i]));
                                            if (tempx >= minX && tempx <= maxX && tempy >= minY && tempy <= maxY && OnePointIsInArea(tempx, tempy, nvert, axisViewPoints)) { indexs.Add(i); }
                                        }
                                    });
                                    if (!result.IsCompleted) return null;
                                }
                                catch (System.OperationCanceledException) { return null; }
                            }
                            else
                            {
                                try
                                {
                                    var result = Parallel.ForEach(parentIndexs, new ParallelOptions() { CancellationToken = token, MaxDegreeOfParallelism = maxDegreeOfParallelism }, (i, loop) =>
                                    {
                                        if (loop.IsStopped) return;
                                        if (xSource.Count <= i || ySource.Count <= i || token.IsCancellationRequested) loop.Stop();
                                        else
                                        {
                                            var tempx = XAxisFunc(xValueConverter == null ? Convert.ToDouble(xSource[i]) : xValueConverter(xSource[i]));
                                            var tempy = YAxisFunc(yValueConverter == null ? Convert.ToDouble(ySource[i]) : yValueConverter(ySource[i]));
                                            if (tempx >= minX && tempx <= maxX && tempy >= minY && tempy <= maxY && OnePointIsInArea(tempx, tempy, nvert, axisViewPoints)) { indexs.Add(i); }
                                        }
                                    });
                                    if (!result.IsCompleted) return null;
                                }
                                catch (System.OperationCanceledException) { return null; }
                            }
                            return indexs.ToArray();
                        }
                        else return null;
                    }, CancelTokenSource.Token, CancelTokenSource.Token);

                    if (Areas == null || Areas.Length != 1 || indexstemp == null) return;
                    if (CancelTokenSource != null)
                    {
                        CancelTokenSource.Dispose();
                        CancelTokenSource = null;
                    }
                    Areas[0].InsideIndexs = indexstemp;
                    IsRefreshingAreaSource = false;
                }
                catch (TaskCanceledException) { }
            }
            else
            {
                Areas[0].InsideIndexs = null;
            }
        }
        private bool OnePointIsInArea(double tempx, double tempy, int nvert, List<Point> axisViewPoints)
        {
            int i, j, c = 0;
            for (i = 0, j = nvert - 1; i < nvert; j = i++)
            {
                var pointI = axisViewPoints[i];
                var pointJ = axisViewPoints[j];
                if (((pointI.Y > tempy) != (pointJ.Y > tempy)) && (tempx < (pointJ.X - pointI.X) * (tempy - pointI.Y) / (pointJ.Y - pointI.Y) + pointI.X))
                {
                    c = 1 + c;
                }
            }
            return (c % 2 != 0);
        }

        /// <summary>
        /// 获取门的数据类型
        /// </summary>
        /// <returns></returns>
        public override BaseGraphicalModel GetGraphicalMode()
        {
            if (IsCreateing) return null;
            return new PolygonGraphicalModel() { AreaNames = new string[] { Areas[0].Name }, AreaColors = new Color[] { Areas[0].DisplayColor }, Points = this.Points };
        }
    }


    public class PolygonGraphicalModel : BaseGraphicalModel
    {
        public List<Point> Points { get; set; }
    }
}
